package com.zhang.gateway.common.handler;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhang.gateway.common.model.constant.HeaderConstant;
import com.zhang.gateway.common.model.constant.RedisConstant;
import com.zhang.gateway.common.model.exception.InvalidTokenException;
import com.zhang.gateway.common.model.exception.NoRightException;
import com.zhang.gateway.feign.AuthRemote;
import com.zhang.gateway.feign.model.ExceptionConstant;
import com.zhang.gateway.feign.model.KingHoodException;
import com.zhang.gateway.feign.model.KingHoodResponseVO;
import com.zhang.gateway.feign.model.authority.GetUserReqDTO;
import com.zhang.gateway.feign.model.authority.UserResDTO;
import com.zhang.gateway.properties.BaseProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.List;
import java.util.Objects;

/**
 * @Copyright 深圳金雅福控股集团有限公司
 * @Author: zhangfanjun
 * @Date 2021/11/17
 * @Version: 1.0
 */
@RequiredArgsConstructor
@Slf4j
@Component
@Order(value = 1)
public class RequestHeaderSetHandler implements GlobalFilter {

    private final BaseProperties baseProperties;
    private final AuthRemote oauthRemote;
    @Resource(name = "stringReactiveRedisTemplate")
    private ReactiveRedisTemplate<String,String> myReactiveRedisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String requestUrl = exchange.getRequest().getPath().value();
        log.info("请求uri：{}", requestUrl);
        //判断是否在白名单的范围
        boolean white = false;
        List<String> ignoreUriList = baseProperties.getIgnoreUriList();
        if (!CollectionUtils.isEmpty(ignoreUriList) && checkUrl(ignoreUriList, requestUrl)) {
            log.info("白名单放行");
            white = true;
        } else if (requestUrl.contains("/authorityCenter/login")) {
            log.info("登录放行");
            white = true;
        } else if (requestUrl.contains("/authorityCenter/register")) {
            log.info("注册放行");
            white = true;
        }
        String token = getToken(exchange);
        if (StringUtils.isBlank(token) && white) {
            return chain.filter(exchange);
        }
        if (StringUtils.isBlank(token)) {
            log.info("无效token");
            return Mono.error(InvalidTokenException.getInstance("无效token"));
        }

        String key = RedisConstant.GATEWAY_TOKEN_PREFIX + token;
        boolean finalWhite = white;
        return myReactiveRedisTemplate.opsForValue().get(key).flatMap(value ->{
            UserResDTO user = JSON.parseObject(value,UserResDTO.class);
            return getHandleResult(exchange, chain, requestUrl, finalWhite, user);

        }).switchIfEmpty(Mono.defer(()->{
            KingHoodResponseVO<UserResDTO> res = oauthRemote.getUser(new GetUserReqDTO(token));
            if (!res.isSuccess()) {
                log.error("token访问异常");
                if (Objects.equals(ExceptionConstant.CODE_FALLBACK, res.getCode())) {
                    log.info("访问鉴权熔断");
                    return Mono.error(new KingHoodException(ExceptionConstant.CODE_FALLBACK, res.getMessage()));
                }
                return Mono.error(InvalidTokenException.getInstance(res.getMessage()));
            }
            if (Objects.isNull(res.getData())) {
                log.error("无效token");
                return Mono.error(InvalidTokenException.getInstance(res.getMessage()));
            }
            UserResDTO userResDTO = res.getData();

            myReactiveRedisTemplate.opsForValue().set(key, JSON.toJSONString(userResDTO))
                    .flatMap(result -> myReactiveRedisTemplate.expire(key, Duration.ofSeconds(baseProperties.getTokenCacheTime())));

            return getHandleResult(exchange, chain, requestUrl, finalWhite, userResDTO);
        }));
    }

    private Mono<Void> getHandleResult(ServerWebExchange exchange, GatewayFilterChain chain, String requestUrl, boolean finalWhite, UserResDTO user) {
        if (!finalWhite && (Objects.isNull(user.getAuthority())
                || CollectionUtils.isEmpty(user.getAuthority().getUrlList())
                || !checkUrl(user.getAuthority().getUrlList(), requestUrl))) {
            log.error("{},非法访问", user);
            return Mono.error(NoRightException.getInstance("非法访问"));
        }
        //将用户信息封装到请求头，也可以采用base64进行加密放一个请求头中，微服务采用过滤器进行解密
        ServerHttpRequest tokenRequest = exchange.getRequest().mutate()
                .header(HeaderConstant.HEADER_USER_Id, user.getUser().getId().toString())
                .header(HeaderConstant.HEADER_USERNAME, user.getUser().getUsername())
                .header(HeaderConstant.HEADER_ISSUER, user.getUser().getIssuer())
                .build();
        ServerWebExchange exchangeNew = exchange.mutate().request(tokenRequest).build();

        return chain.filter(exchangeNew);
    }

    /**
     * 这里需要考虑白名单的写法是/**
     * 这里不考虑*h号在中间的情况
     */
    private boolean checkUrl(List<String> ignoreUriList, String requestUrl) {
        for (String ignore : ignoreUriList) {
            if (ignore.equals(requestUrl)) {
                return true;
            }
            if (ignore.contains("*")) {
                String uri = ignore.substring(0, ignore.indexOf("*"));
                if (requestUrl.startsWith(uri)) {
                    return true;
                }
            }
        }
        return false;
    }

    private String getToken(ServerWebExchange exchange) {
        List<String> authorization = exchange.getRequest().getHeaders().get("Authorization");
        if (CollectionUtils.isEmpty(authorization)) {
            return null;
        }
        String bear = authorization.get(0);
        if (bear.contains("Bearer ")) {
            String token = bear.substring("Bearer ".length());
            log.info("token:{}", token);
            return token;
        }
        return null;
    }
}
